#ifndef __PCODE_H
#define __PCODE_H 1

#include <string>
#include <instruction.h>
#include <common.h>

enum PcodeType : int {
    kLabel = 0,
    kRtype = 1,
    kItype = 2,
    kJtype = 3,
};



struct Pcode {
    PcodeType _type = kLabel;
    std::string _label;
private:
    uint32_t _code = 0;

    Pcode rmake(int rs, int rt, int rd, int func, int shmat = 0) {
        _code = ((rs&0x1F)<<21) | ((rt&0x1F)<<16) | ((rd&0x1F)<<11) | ((shmat&0x1F)<<6) | (func & 0x3F);
        _type = kRtype;
        return *this;
    }

    Pcode imake(int op, int rs, int rt) {
        _code = (op <<26) | ((rs&0x1F)<<21) | ((rt&0x1F)<<16);
        _type = kItype;
        return *this;
    }
    Pcode imm(int val) {
        if (_type == kItype)
            _code |= (0xFFFF & val);
        else if (_type == kJtype)
            _code |= (0x3FFFFFF & val);
        return *this;
    }
    Pcode lbl(std::string lable) {
        _label = lable;
        return *this;
    }
    Pcode jmake(int op) {
        _type = kJtype;
        _code = (op << 26);
        return *this;
    }
public:

    void set(int val) {
        _code &= ~(0x1F<<11);
        _code |= (val&0x1F) << 11;
    }

    Instruction emit() const { return Instruction{_code};}

    static Pcode label(std::string label) { 
        return Pcode().lbl(label);
    }
    static Pcode addiu(int rs, int rt, int imm) {
        return Pcode().imake(011, rs, rt).imm(imm);
    }
    static Pcode li(int rt, int imm) {
        return addiu(zero, rt, imm);
    }

    static Pcode sw(int rs, int rt, int imm) {
        return Pcode().imake(053, rs, rt).imm(imm);
    }
    static Pcode lw(int rs, int rt, int imm) {
        return Pcode().imake(043, rs, rt).imm(imm);
    }
    static Pcode b(std::string label) {
        return beq(zero, zero, label);
    }

    static Pcode beq(int rs, int rt, std::string label) {
        return Pcode().imake(004, rs, rt).lbl(label);
    }
    static Pcode bne(int rs, int rt, std::string label) {
        return Pcode().imake(005, rs, rt).lbl(label);
    }
   
    static Pcode j(std::string label) {
        return Pcode().jmake(002).lbl(label);
    }
    static Pcode jal(std::string label) {
        return Pcode().jmake(003).lbl(label);
    }

    static Pcode lui(int rt, int imm) {
        return Pcode().imake(017, 0, rt).imm(imm);
    }
    static Pcode xori(int rs, int rt, int imm) {
        return Pcode().imake(016, rs, rt).imm(imm);
    }

    static Pcode add(int rs, int rt, int rd) {
        return Pcode().rmake(rs, rt, rd, 040);
    }

    static Pcode addu(int rs, int rt, int rd) {
        return Pcode().rmake(rs, rt, rd, 041);
    }
    static Pcode sub(int rs, int rt, int rd) {
        return Pcode().rmake(rs, rt, rd, 042);
    }
    static Pcode subu(int rs, int rt, int rd) {
        return Pcode().rmake(rs, rt, rd, 043);
    }
    static Pcode vm_and(int rs, int rt, int rd) {
        return Pcode().rmake(rs, rt, rd, 044);
    }
    static Pcode move(int rs, int rd) {
        return vm_or(rs, zero, rd);
    }
    static Pcode vm_or(int rs, int rt, int rd) {
        return Pcode().rmake(rs, rt, rd, 045);
    }   

    static Pcode vm_xor(int rs, int rt, int rd) {
        return Pcode().rmake(rs, rt, rd, 046);
    }
    static Pcode nor(int rs, int rt, int rd) {
        return Pcode().rmake(rs, rt, rd, 047);
    }
    static Pcode slt(int rs, int rt, int rd) {
        return Pcode().rmake(rs, rt, rd, 052);
    }
    static Pcode sltu(int rs, int rt, int rd) {
        return Pcode().rmake(rs, rt, rd, 053);
    }
    static Pcode sll(int rt, int rd, int shamt) {
        return Pcode().rmake(0, rt, rd, 000, shamt);
    }
    static Pcode srl(int rt, int rd, int shamt) {
        return Pcode().rmake(0, rt, rd, 002, shamt);
    }
    
    static Pcode sra(int rt, int rd, int shamt) {
        return Pcode().rmake(0, rt, rd, 003, shamt);
    }
    static Pcode sllv(int rs, int rt, int rd) {
        return Pcode().rmake(rs, rt, rd, 004);
    }
    static Pcode srlv(int rs, int rt, int rd) {
        return Pcode().rmake(rs, rt, rd, 006);
    }
    static Pcode srav(int rs, int rt, int rd) {
        return Pcode().rmake(rs, rt, rd, 007);
    }
    static Pcode jr(int rs) {
        return Pcode().rmake(rs, zero, zero, 010);
    }
    static Pcode mfhi(int rd) {
        return Pcode().rmake(0, 0, rd, 020);
    }
    static Pcode mthi(int rs) {
        return Pcode().rmake(rs, 0, 0, 021);
    }
    static Pcode mflo(int rd) {
        return Pcode().rmake(0, 0, rd, 022);
    }
    static Pcode mtlo(int rs) {
        return Pcode().rmake(rs, 0, 0, 023);
    }
    static Pcode mult(int rs, int rt) {
        return Pcode().rmake(rs, rt, 0, 030);
    }
    static Pcode multu(int rs, int rt) {
        return Pcode().rmake(rs, rt, 0, 031);
    }
    static Pcode vm_div(int rs, int rt) {
        return Pcode().rmake(rs, rt, 0, 032);
    }
    static Pcode divu(int rs, int rt) {
        return Pcode().rmake(rs, rt, 0, 033);
    }

    static Pcode nop() {
        return Pcode().rmake(0, 0, 0, 0, 0);
    }
};

#endif // __PCODE_H